#include "AzXmlDataSource.h"


const char DATAPACKET_TAG[]         = "DATAPACKET";
const char METADATA_TAG[]           = "METADATA";
const char ROWS_TAG[]               = "ROWS";
const char FIELDS_TAG[]             = "FIELDS";
const char FIELD_TAG[]              = "FIELD";

const char ROWDATA_TAG[]            = "ROWDATA";
const char RECORDSTATE_ATTR[]       = "RecordState";
const char ROW_TAG[]                = "ROW";
const char OLD_ROW_TAG[]            = "OLDROW";
const char DELTA_TAG[]              = "DELTA";
const char DELTA_STATE_ATTR[]       = "RecordState";
const char DATAPACKETTYPE_ATTR[]    = "DataPacketType";
const char VERSION_ATTR[]           = "Version";
const char NAME_ATTR[]              = "Name";


#include "Az.h"

#include <QDebug>
#include <QMetaProperty>

#include "AzField.h"
#include "AzRootRecord.h"


/*!
  \class AzXmlDataSource
  Записывает данные в формате XML
  Данные записываются/читаются либо в(из) файл(а) - нужно определить его имя \sa setFielName, либо в поток \sa setDevice
  При записи данных в поток не позиционирования не происходит, указателем на позицию в потоке должно управляться вне класса.

  Внутрення архитектура.

  mDevice - всегда указывает на текущий поток (либо mFile, либо mDevice). Понятно, если какой то из них существуют.
  Если участвует \a mFile - управление по позиционированию потока ложится на класс, иначе внешнее.

*/


/*!
 * \brief AzXmlDataSourceException::AzXmlDataSourceException
 * \param [in] errCode
 * \param [in] nativeErrMessage
 * \param [in] arg2
 */
AzXmlDataSourceException::AzXmlDataSourceException(XmlError errCode,const QString& nativeErrMessage,const QString& arg2)
    :AzDataSourceError(nativeCodeToCode(errCode),errCode,nativeCodeToMessage(errCode)) {

    if (mNativeErrorMessage.isEmpty()) {
        mNativeErrorMessage = nativeErrMessage;
    } else {
        mNativeErrorMessage = mNativeErrorMessage.arg(nativeErrMessage);
    }
    if (!arg2.isEmpty()) {
        mNativeErrorMessage = mNativeErrorMessage.arg(arg2);
    }
}

AzXmlDataSourceException::AzXmlDataSourceException(AzDataSourceException::Error error):AzDataSourceError(error) {

}

QString AzXmlDataSourceException::nativeCodeToMessage(XmlError errCode) {
    switch (errCode) {
    case NoDeviceSpecifyError:
        return "You must specified device";
    case XmlExcpectTagError:
        return "Expect XML Tag %1, but found %2";
    case XmlExpectAttrError:
        return "Expect XML attribute %1, but found %2";
    default:
        qFatal("AzXmlDataSourceException: not define native code message");
    }
}

AzDataSourceException::Error AzXmlDataSourceException::nativeCodeToCode(XmlError errCode) {
    switch (errCode) {
    case NoDeviceSpecifyError:
        return AzDataSourceException::ConnectionError;
    case OpenFileError:
        return AzDataSourceException::OpenError;
    case XmlExcpectTagError:
    case XmlExpectAttrError:
        return AzDataSourceException::WrongDataFormatError;
    default:
        return AzDataSourceException::UnknownError;
    }
}

/*!
    Default constructor
*/
AzXmlDataSource::AzXmlDataSource(QObject *parent):AzAbstractDataSource(Az::DeltaPacket | Az::MetadataPacket | Az::DataPacket,parent),mDevice(0) {
    mXmlStreamWriter.setAutoFormatting(true);
}

AzXmlDataSource::~AzXmlDataSource() {
}

/*!
 * \brief AzXmlFileDataSource::isConnectReadOnly
 * \return true, if connection readOnly, otherwise return false
 * \Note - if connection not open, return true
 */
bool AzXmlDataSource::isConnectReadOnly() const {
    if (!isOpen() && mDevice == 0)
        return true;
    return !mDevice->isWritable();
}

/*!
 * \brief AzXmlDataSource::setDevice Устанавливаем внешний поток.
 * \param dev - внешний поток
 * Используется внешний поток \a dev
 */
void AzXmlDataSource::setDevice(QIODevice* dev) {
    mDevice = dev;
}


/*!
 * \brief AzXmlDataSource::estabsilshConnection Проверяет режим открытия потока
 * Проверяет режим открытия потока \sa setDevice(). Если поток открыт не в том режиме, вызывает исключение
 */

void AzXmlDataSource::estabsilshConnection() {
    if (mDevice == 0) {
        throw AzXmlDataSourceException(AzXmlDataSourceException::NoDeviceSpecifyError);
    }
    if (!mDevice->isOpen())
        throw AzXmlDataSourceException(AzDataSourceException::NotOpenError);
    if (readOnly() && !mDevice->isReadable())
        throw AzXmlDataSourceException(AzDataSourceException::WriteOnlyError);
    if (!readOnly() && !mDevice->isWritable())
        throw AzXmlDataSourceException(AzDataSourceException::ReadOnlyError);
}

/*!
 * \brief AzXmlFileDataSource::open
 *
 */
void AzXmlDataSource::internalOpen() {
    mXmlStreamReader.setDevice(mDevice);
    mXmlStreamWriter.setDevice(mDevice);
}

/*!
 * \brief AzXmlFileDataSource::internalClose
 * close connection and release file resource
 */
void AzXmlDataSource::internalClose() {
}


/*!
 * \brief AzXmlDataSource::doBeforeRead
 * read XML METADATA tag
 */

void AzXmlDataSource::doBeforeRead() {
    mXmlStreamReader.readNextStartElement();
    if (mXmlStreamReader.name() != DATAPACKET_TAG)
        throw AzXmlDataSourceException(AzXmlDataSourceException::XmlExcpectTagError,DATAPACKET_TAG,mXmlStreamReader.name().toString());
    if (mXmlStreamReader.attributes().hasAttribute(NAME_ATTR)) {
        qDebug() << "Read attr name:" << mXmlStreamReader.attributes().value(NAME_ATTR).toString();
        setName(mXmlStreamReader.attributes().value(NAME_ATTR).toString());
    }
}

/*!
 * \brief AzXmlDataSource::readMetadata
 * \param stream
 * \return Метаданные
 * Считывает метаданные и устанавливает позицию на теге, следующем за <METADATA_TAG>
 */
AzFieldList AzXmlDataSource::readMetadata() {
    mXmlStreamReader.readNextStartElement();
    if (mXmlStreamReader.name() != METADATA_TAG) {
        throw AzXmlDataSourceException(AzXmlDataSourceException::XmlExcpectTagError,METADATA_TAG,mXmlStreamReader.name().toString());
    }
    mXmlStreamReader.readNextStartElement();
    if (mXmlStreamReader.name() != FIELDS_TAG) {
        throw AzXmlDataSourceException(AzXmlDataSourceException::XmlExcpectTagError,FIELDS_TAG,mXmlStreamReader.name().toString());
    }
    mXmlStreamReader.readNextStartElement();
    if (mXmlStreamReader.name() != FIELD_TAG)
        throw AzXmlDataSourceException(AzXmlDataSourceException::XmlExcpectTagError,FIELD_TAG,mXmlStreamReader.name().toString());
    AzFieldList fl;
    do { //read FIELDS TAG
        AzField f;
        if (mXmlStreamReader.tokenType() == QXmlStreamReader::StartElement) {
            f = readField();
            fl.append(f.name(), f);
        #ifdef AZ_DEBUG
                    qDebug() << "Load field :" << f.name();
        #endif
        }
        mXmlStreamReader.readNext();
    } while ((mXmlStreamReader.name() == FIELD_TAG) || mXmlStreamReader.tokenType() == QXmlStreamReader::Characters);
    mXmlStreamReader.readNextStartElement(); //skip end FIELDS
    mXmlStreamReader.readNextStartElement(); //skip  end METADATA
    return fl;
}



/*!
 * \brief QXmlStreamReader::loadField
 * load single tag FIELD
 * \return AzField
 */
AzField AzXmlDataSource::readField() {
    AzField f;
    QXmlStreamAttributes attr = mXmlStreamReader.attributes();
    const QMetaObject *meta = f.metaObject();
    for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
        QMetaProperty prop = meta->property(i);
        QVariant val;
        switch (prop.type()) {
        case QVariant::String:
            val = attr.value(prop.name()).toString();
            break;
        case QVariant::Int:
            val = attr.value(prop.name()).toInt();
            break;
        default:
            throw AzDataSourceException(AzDataSourceException::UnsupportedFieldTypeError);
        }
        if (!prop.write(&f, val))
            throw AzDataSourceException(AzDataSourceException::UnknownError);
    }
    return f;
}


void AzXmlDataSource::writeMetadata(const AzFieldList& fields) {
    mXmlStreamWriter.writeStartElement(METADATA_TAG);
    mXmlStreamWriter.writeStartElement(FIELDS_TAG);
    for (int i = 0; i < fields.size();++i) {
        mXmlStreamWriter.writeStartElement(FIELD_TAG);
        const QMetaObject *meta = fields[i].metaObject();
        for (int j = meta->propertyOffset(); j < meta->propertyCount(); ++j) {
            QMetaProperty prop = meta->property(j);
            mXmlStreamWriter.writeAttribute(prop.name(), prop.read(&fields[i]).toString());
        }
        mXmlStreamWriter.writeEndElement();
    }
    mXmlStreamWriter.writeEndElement();
    mXmlStreamWriter.writeEndElement();
}

void AzXmlDataSource::doBeforeWriteChildRecords(const AzRecord*) {
    mXmlStreamWriter.writeStartElement(ROWS_TAG);
}

void AzXmlDataSource::doAfterWriteChildRecords(const AzRecord*) {
    mXmlStreamWriter.writeEndElement();
}


/*!
 * \brief AzXmlDataSource::doBeforeWrite Вызывается перед записью потока
 * Записывает корневой тег (DATAPACKET_TAG) и его атрибуты.
 * \sa doAfterWrite()
 */
void AzXmlDataSource::doBeforeWrite() {
    mXmlStreamWriter.writeStartElement(DATAPACKET_TAG);
    mXmlStreamWriter.writeAttribute(DATAPACKETTYPE_ATTR,QString::number(dataPacketFlags()));
    mXmlStreamWriter.writeAttribute(VERSION_ATTR, "1");
    if (!name().isEmpty())
        mXmlStreamWriter.writeAttribute(NAME_ATTR, name());
}

/*!
 * \brief AzXmlDataSource::doAfterWrite Вызывается после окончания записи в поток.
 * Закрывает корневой тег ХМЛ - DATAPACKET_TAG
 * \sa doBeforeWrite()
 */
void AzXmlDataSource::doAfterWrite() {
    mXmlStreamWriter.writeEndElement();
}


/*!
 * \brief AzXmlDataSource::writeRecord
 * \param rec Записываемая запись
 * Записывает запись в XML. Должна вызываться только, если dataPacketFlags() содержит  Az::DataPacket и/или Az::DeltaPacket
 *
 */
void AzXmlDataSource::writeRecord(const AzRecord* rec) {
    bool isOnlyDataPacket = !(dataPacketFlags() & Az::DeltaPacket);
    Az::RecordState recState = isOnlyDataPacket? Az::Unmodified : rec->recordState();
    mXmlStreamWriter.writeStartElement(ROWDATA_TAG);
    mXmlStreamWriter.writeAttribute(RECORDSTATE_ATTR,AzRecord::recordStateString(recState));
    mXmlStreamWriter.setAutoFormatting(false);

    bool isOldDataWrite = (isOnlyDataPacket && (rec->recordState() == Az::Modified)); //если запись модифицирована,  выстаскиваем старые данные
    const AzRecordValues &data = isOldDataWrite ? rec->oldData() : rec->data();
    writeRecord(data,ROW_TAG);

    if (dataPacketFlags() & Az::DeltaPacket) {
        if (rec->recordState() == Az::Modified)
            writeRecord(rec->oldData(),OLD_ROW_TAG);
    }
    mXmlStreamWriter.writeEndElement();
    mXmlStreamWriter.setAutoFormatting(true);
}

/*!
 * \brief AzXmlDataSource::writeRecord
 * \param data записываемые данные
 * \param tag таг
 */
void AzXmlDataSource::writeRecord(const AzRecordValues& data,const char *tag) {
    mXmlStreamWriter.writeStartElement(tag);
    const AzFieldList &fields = AzXmlDataSource::fields();
    for (int i = 0; i < fields.size(); ++i) { //save rec data
       mXmlStreamWriter.writeAttribute(fields[i].name(),data[i].toString());
    }
    mXmlStreamWriter.writeEndElement();
}

/*!
 * \brief AzXmlDataSource::readRecords
 * \return Количество считаных записей
 * Позиция \a mXmlStreamReader должен указывать на тег <ROWS_TAG>
 */
int AzXmlDataSource::readRecords(AzRecord*, const AzRecord*,int) {

    if (mXmlStreamReader.name() != ROWS_TAG) {
        throw AzXmlDataSourceException(AzXmlDataSourceException::XmlExcpectTagError,ROWS_TAG,mXmlStreamReader.name().toString());
    }
    mXmlStreamReader.readNextStartElement();
   if (mXmlStreamReader.name() == ROWDATA_TAG)
       readRecord();

    qDebug() << mXmlStreamReader.name();
}

/*!
 * \brief AzXmlDataSource::readRecord
 * \return Считанные данные
 * Считывает запись с тега <ROWDATA_TAG>. Поток должен указывать на тег <ROWDATA_TAG>
 * После считывания поток указывает на следующий XML элемент за <ROWDATA_TAG>
 * \Warning - тег должен быть быть <ROWDATA_TAG>, не производит проверку.
 * \sa readRow()
 */
AzRecordData AzXmlDataSource::readRecord() {
    //QXmlStreamAttributes attr = mXmlStreamReader.attributes();
    //return AzRecordData();
}

/*!
 * \brief AzXmlDataSource::readRow
 * \return 
 * Считываем тег ROWS_TAG. Поток должен указывать на таг, не производяться проверки.
 */
AzRecordValues AzXmlDataSource::readRow() {
    QXmlStreamAttributes attr = mXmlStreamReader.attributes();
    const AzFieldList &fields = this->fields();
    AzRecordValues res = fields; //заполняем типом полей
    qDebug() << res.size();

    for (int i = 0; i < fields.size(); ++i) {
        if (fields[i].fieldKind() != AzField::DataFieldKind) {
            res.append(fields[i].type()); //add empty value
            continue;
        }
        if (attr.hasAttribute(fields[i].name())) {
           switch (fields[i].type()) {
               case QVariant::Invalid:
                   throw AzDataSourceException(AzDataSourceException::InvalidFieldTypeError);
               case QVariant::String:
                   res.append(attr.value(fields[i].name()).toString());
                   break;
               case QVariant::Int:
                   res.append(attr.value(fields[i].name()).toInt());
                   break;
               default:
                   throw AzDataSourceException(AzDataSourceException::UnsupportedFieldTypeError);
                   break;
               }
           }

    }
}
